package middleware

import (
	"fmt"
	"io"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/mattn/go-isatty"

	"github.com/marmotedu/iam/pkg/log"
)

// defaultLogFormatter 是 Logger 中间件使用的默认日志格式函数
var defaultLogFormatter = func(param gin.LogFormatterParams) string {
	// 定义状态码颜色、方法颜色和重置颜色的变量
	var statusColor, methodColor, resetColor string
	// 如果需要输出颜色
	if param.IsOutputColor() {
		// 获取状态码对应的颜色
		statusColor = param.StatusCodeColor()
		// 获取请求方法对应的颜色
		methodColor = param.MethodColor()
		// 获取重置颜色的字符串
		resetColor = param.ResetColor()
	}

	// 如果请求处理时间超过一分钟
	if param.Latency > time.Minute {
		// 以兼容 Go 1.8 之前版本的方式截断时间
		param.Latency = param.Latency - param.Latency%time.Second
	}

	// 格式化日志输出字符串
	return fmt.Sprintf("%s%3d%s - [%s] \"%v %s%s%s %s\" %s",
		// param.TimeStamp.Format("2006/01/02 - 15:04:05"),
		statusColor, param.StatusCode, resetColor,
		param.ClientIP,
		param.Latency,
		methodColor, param.Method, resetColor,
		param.Path,
		param.ErrorMessage,
	)
}

// Logger 实例化一个 Logger 中间件，该中间件会将日志写入 gin.DefaultWriter。
// 默认情况下 gin.DefaultWriter 为 os.Stdout。
func Logger() gin.HandlerFunc {
	// 调用 LoggerWithConfig 函数，使用默认配置
	return LoggerWithConfig(GetLoggerConfig(nil, nil, nil))
}

// LoggerWithFormatter 实例化一个带有指定日志格式函数的 Logger 中间件。
func LoggerWithFormatter(f gin.LogFormatter) gin.HandlerFunc {
	// 调用 LoggerWithConfig 函数，传入包含指定格式化函数的配置
	return LoggerWithConfig(gin.LoggerConfig{
		Formatter: f,
	})
}

// LoggerWithWriter 实例化一个带有指定写入缓冲区的 Logger 中间件。
// 示例：os.Stdout、以写入模式打开的文件、套接字等。
func LoggerWithWriter(out io.Writer, notlogged ...string) gin.HandlerFunc {
	// 调用 LoggerWithConfig 函数，传入包含指定写入器和跳过路径的配置
	return LoggerWithConfig(gin.LoggerConfig{
		Output:    out,
		SkipPaths: notlogged,
	})
}

// LoggerWithConfig 实例化一个带有配置的 Logger 中间件。
func LoggerWithConfig(conf gin.LoggerConfig) gin.HandlerFunc {
	// 获取配置中的格式化函数
	formatter := conf.Formatter
	// 如果格式化函数为空
	if formatter == nil {
		// 使用默认的格式化函数
		formatter = defaultLogFormatter
	}

	// 获取配置中的输出目标
	out := conf.Output
	// 如果输出目标为空
	if out == nil {
		// 使用 gin 的默认输出目标
		out = gin.DefaultWriter
	}

	// 获取配置中需要跳过的路径
	notlogged := conf.SkipPaths

	// 标记是否为终端输出，默认为是
	isTerm := true

	// 判断输出目标是否为文件
	if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
		(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
		// 如果不是文件，或者终端环境不支持，标记为非终端输出
		isTerm = false
	}

	// 如果是终端输出
	if isTerm {
		// 强制控制台输出颜色
		gin.ForceConsoleColor()
	}

	// 存储需要跳过的路径的映射
	var skip map[string]struct{}

	// 获取需要跳过的路径的数量
	if length := len(notlogged); length > 0 {
		// 初始化映射
		skip = make(map[string]struct{}, length)

		// 遍历需要跳过的路径
		for _, path := range notlogged {
			// 将路径添加到映射中
			skip[path] = struct{}{}
		}
	}

	// 返回一个 gin 处理函数
	return func(c *gin.Context) {
		// 开始计时
		start := time.Now()
		// 获取请求的路径
		path := c.Request.URL.Path
		// 获取请求的原始查询字符串
		raw := c.Request.URL.RawQuery

		// 处理请求
		c.Next()

		// 仅当路径不在跳过列表中时记录日志
		if _, ok := skip[path]; !ok {
			// 初始化日志格式化参数
			param := gin.LogFormatterParams{
				Request: c.Request,
				Keys:    c.Keys,
			}

			// 停止计时
			param.TimeStamp = time.Now()
			// 计算请求处理时间
			param.Latency = param.TimeStamp.Sub(start)

			// 获取客户端 IP
			param.ClientIP = c.ClientIP()
			// 获取请求方法
			param.Method = c.Request.Method
			// 获取响应状态码
			param.StatusCode = c.Writer.Status()
			// 获取错误信息
			param.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()

			// 获取响应体大小
			param.BodySize = c.Writer.Size()

			// 如果有原始查询字符串
			if raw != "" {
				// 拼接路径和查询字符串
				path = path + "?" + raw
			}

			// 设置最终的请求路径
			param.Path = path

			// 记录日志
			log.L(c).Info(formatter(param))
		}
	}
}
